<?php

namespace Application\Model\Dao;

use Zend\Config\Reader\Xml;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Db\ResultSet\ResultSet;
use Application\Service\XmlService;
use Zend\Db\Adapter\Driver\Mysqli\Statement;
use Zend\Form\Annotation\Object;

/**
 * Class GraphDao
 */
class GraphDao extends BaseDao
{


    public static $node_attribute_table_array = array(
        'attribute_node_date' => 'attnd',
        'attribute_node_int' => 'ani',
        'attribute_node_long' => 'anl',
        'attribute_node_sm_string' => 'anss',
        'attribute_node_lg_string' => 'anls',
        'attribute_node_lg_blob' => 'anlb'
    );


    public static $edge_attribute_table_array = array(
        'attribute_edge_date'=> 'atted',
        'attribute_edge_int'=> 'aei',
        'attribute_edge_long'=> 'ael',
        'attribute_edge_sm_string'=> 'aess',
        'attribute_edge_lg_string'=> 'aels',
        'attribute_edge_lg_blob'=> 'aelb'
    );

    public static $attribute_select = array(
        'id',
        'attr_name',
        'attr_val',
        'node'
    );


    public static $INSERT_NODE = 'insert into node values()';

    public static $INSERT_EDGE = 'insert into edge(node_from, node_to, label ) values(?, ?, ?)';

    public static $INSERT_ATTR = 'insert into attribute_#ne#_#type#(attr_name, attr_val, #ne#  ) values(?, ?, ?)';



    /**
     * @var string Get methods for node.
     */


    public static $GET_NODE = 'select #selectField# from node ';



    /**
     * @var string
     */
    public static $GET_NODE_LEFT_JOINT_STATEMENT = 'left join #table# as #tableAlias# on (node.id = #tableAlias#.node) ';



    /**
     * @var string
     */
    public static $GET_EDGE_JOINT_STATEMENT = 'left join #table# as #tableAlias# on (edge.#node# = #tableAlias#.node) ';



    /**
     * @var string
     */
    public static $GET_NODE_WHERE_STATEMENT = 'where node.id = ? ';


    // right node.
    public static $GET_NODE_FROM_EDGE = 'select #selectField# from edge #join# where #node# = ?  ';


    // right node with label.
    public static $GET_NODE_FROM_EDGE_WITH_LABEL = 'select #selectField# from edge #join#  where #node# = ? and label = ?  ';



    private function getType($obj)
    {
        switch (gettype($obj)) {
            case 'integer':
                if (abs($obj) < 2 ^ 31) {
                    return 'int';
                }
                return 'long';
                break;
            case 'string':
                if (strlen($obj) <= 255) {
                    return 'sm_string';
                }
                return 'lg_string';
                break;

            default:

                if (is_array($obj)) {
                    if ($obj['type'] == 'blob') {
                        return 'blob';
                    }

                    if ($obj['type'] == 'date') {
                        return 'date';
                    }
                }
                break;
        }
        throw new \Zend\Json\Server\Exception\ErrorException('This node contains bad attribute');
    }

    private function getValue($obj)
    {
        if (is_array($obj)) {
            return $obj['val'];
        } else {
            return $obj;
        }
    }


    private function saveNodeWithoutTr($node, $connection)
    {

        $stmt = $this->adapter->createStatement(GraphDao::$INSERT_NODE, array());
        $stmt = $stmt->prepare();
        $stmt->execute();
        $stmt = null;

        $nodeId = $connection->getLastGeneratedValue();

        $node['id'] = $nodeId;
        $this->generateInsertStatement($node, 'node');
        return $nodeId;
    }


    public function saveNode($node)
    {

        $connection = $this->adapter->driver->getConnection();
        $connection->beginTransaction();
        try {

            $this->saveNodeWithoutTr($node, $connection);
            $connection->commit();
        } catch (\Exception $e) {

            $connection->rollback();
            throw $e;
        }
    }


    private function generateInsertStatement($obj, $type)
    {
        foreach ($obj as $key => $value) {
            $INSERT_ATTR = str_replace('#ne#', $type, GraphDao::$INSERT_ATTR);
            $INSERT_ATTR = str_replace('#type#', $this->getType($value), $INSERT_ATTR);
            $stmt = $this->adapter->createStatement($INSERT_ATTR, array($key, $this->getValue($value), $obj['id']
            ));

            $stmt = $stmt->prepare();
            $stmt->execute();
        }
    }

    public function saveEdge($edgeProps, $edgeLabel, $fromNode, $toNode)
    {
        $connection = $this->adapter->driver->getConnection();
        $connection->beginTransaction();
        try {

            assert($fromNode != null && $toNode != null && $edgeLabel != null && strlen($edgeLabel) > 0 && $edgeProps != null);

            if ($fromNode['id'] == null) {
                $fromNode['id'] = $this->saveNodeWithoutTr($fromNode, $connection);
            }

            if ($toNode['id'] == null) {
                $toNode['id'] = $this->saveNodeWithoutTr($toNode, $connection);
            }

            $stmt = $this->adapter->createStatement(GraphDao::$INSERT_EDGE, array($fromNode['id'],
                $toNode['id'],
                $edgeLabel
            ));
            $stmt = $stmt->prepare();
            $stmt->execute();
            $stmt = null;
            $edgeId = $connection->getLastGeneratedValue();
            $edgeProps['id'] = $edgeId;
            $this->generateInsertStatement($edgeProps, 'edge');
            $connection->commit();
        } catch (\Exception $e) {
            $connection->rollback();
            throw $e;
        }
    }

    public function getNode($id)
    {

        $joinStatement = '';


        $selectStatement = $this->getSelectFields();

        $selectStatement = str_replace('#selectField#',$selectStatement, GraphDao::$GET_NODE);

        foreach(GraphDao::$node_attribute_table_array as $tableName => $tableNameAlias){

            $joinStmtTemp = str_replace('#table#',$tableName,GraphDao::$GET_NODE_LEFT_JOINT_STATEMENT);
            $joinStmtTemp = str_replace('#tableAlias#',$tableNameAlias,$joinStmtTemp);

            $joinStatement = $joinStatement.$joinStmtTemp;
        }
        $nodeSelectStatemt = $selectStatement. $joinStatement .GraphDao::$GET_NODE_WHERE_STATEMENT;



        $stmt = $this->adapter->createStatement($nodeSelectStatemt, array($id));
        $stmt->prepare();
        $result = $stmt->execute();
        if ($result instanceof ResultInterface && $result->isQueryResult()) {
            $resultSet = new ResultSet();
            $resultSet->initialize($result);
            if ($resultSet->count() > 0) {
                return $this->processNodeObject($resultSet);
            }
            return null;
        }
    }


    private function processNodeObject($resultSet){
        $node = array();

        foreach ($resultSet as $result) {
            foreach(GraphDao::$node_attribute_table_array as $tableName => $tableNameAlias){
                if( $result[$tableNameAlias.'attr_name'] != null){
                    $node[$result[$tableNameAlias.'attr_name']] = $result[$tableNameAlias.'attr_val'];
                }
            }
        }

        return $node;
    }


    private function processNodeArrayObject($resultSet){
        $node = array();

        foreach ($resultSet as $result) {
            if( !array_key_exists($result['nodeid'], $node))
                $node[$result['nodeid']] = array();
            foreach(GraphDao::$node_attribute_table_array as $tableName => $tableNameAlias){
                if( $result[$tableNameAlias.'attr_name'] != null){
                    $node[$result['nodeid']][$result[$tableNameAlias.'attr_name']] = $result[$tableNameAlias.'attr_val'];
                }
            }
        }

        return $node;
    }

    private function getSelectFields(){
        $selectStatement = '';

        $first = true;

        foreach(GraphDao::$node_attribute_table_array as $tableName => $tableNameAlias){

            $select = '';

            foreach(GraphDao::$attribute_select as $colName){
                if($first){
                    $select = $select.$tableNameAlias.'.'.$colName.' as '.$tableNameAlias.$colName;
                } else {
                    $select = $select.' ,'.$tableNameAlias.'.'.$colName.' as '.$tableNameAlias.$colName;
                }
                $first = false;
            }
            $selectStatement = $selectStatement.$select;
        }

        return $selectStatement;
    }


    private function getFetchStatementForNodesChildren($dir, $encodedSelectStatement){
        $fromNode = ($dir === 'node_from');

        $selectStatement = $this->getSelectFields();

        if($fromNode){
            $selectStatement = $selectStatement.', edge.node_to as nodeid ';
        } else {
            $selectStatement = $selectStatement.', edge.node_from as nodeid ';
        }

        $selectStatement = str_replace('#selectField#',$selectStatement,
            $encodedSelectStatement);

        $selectStatement = str_replace('#node#',$dir,
            $selectStatement);


        $joinStatement = '';
        foreach(GraphDao::$node_attribute_table_array as $tableName => $tableNameAlias){

            $joinStmtTemp = str_replace('#table#',$tableName,
                str_replace('#node#',$fromNode ? 'node_to' : 'node_from',   GraphDao::$GET_EDGE_JOINT_STATEMENT));

            $joinStmtTemp = str_replace('#tableAlias#',$tableNameAlias,$joinStmtTemp);
            $joinStatement = $joinStatement.$joinStmtTemp;
        }

        $selectStatement = str_replace('#join#',$joinStatement,
            $selectStatement);

        return$selectStatement;

    }


    private function fetchNodeFromEdgeResult($statement, $params){
        $stmt = $this->adapter->createStatement($statement, $params);
        $stmt->prepare();
        $result = $stmt->execute();
        if ($result instanceof ResultInterface && $result->isQueryResult()) {
            $resultSet = new ResultSet();
            $resultSet->initialize($result);
            if ($resultSet->count() > 0) {
                echo $resultSet->count();
                return $this->processNodeArrayObject($resultSet);
            }
            return null;
        }
    }


    public function getChildren($nodeId, $dir)
    {

        $selectStatement = $this->getFetchStatementForNodesChildren($dir, GraphDao::$GET_NODE_FROM_EDGE);
       // echo $selectStatement;
        return $this->fetchNodeFromEdgeResult($selectStatement, array($nodeId) );
    }

    public function getChildrenWithLabel($nodeId, $dir, $label)
    {

        $selectStatement = $this->getFetchStatementForNodesChildren($dir, GraphDao::$GET_NODE_FROM_EDGE_WITH_LABEL);

        echo $selectStatement;
        return $this->fetchNodeFromEdgeResult($selectStatement, array($nodeId, $label) );

    }

    public function getChildrenFromMultiNode($startNode, $dir)
    {

    }
}